<?php
include_once $_SERVER['DOCUMENT_ROOT'].'/database/db.php';
include_once $_SERVER['DOCUMENT_ROOT']."/lib/spyc.php";
/**
 * Token相关功能类，包括以下功能
 * 1. 生成唯一性Token
 * 2. 验证Token有效性
 * 3. 将token信息存储到数据库(mongodb)
 * 4. 删除数据库中的过期Token
 * 5. 检测用户当日上传文件总大小
 */
class Token
{
    private $issuer;      // Token签发者
    private $timestamp;   // Token签发时间
    private $userId;      // 用户id
    private $type;        // JWT类型
    private $alg;         // 签名算法
    private $signKey;     // 签名密钥
    private $DB;          // 数据库连接
    public function __construct($issuer='Loyalli', $type='JWT', $alg='SHA256')
    {
        $ENV = spyc_load_file($_SERVER['DOCUMENT_ROOT'].'/properties/env.yml');
        $this->userId = mt_rand(10000, 99999);
        $this->timestamp = time();
        $this->signKey = ($ENV['token']['signKey'])['0'];
        $this->issuer = $issuer;
        $this->type = $type;
        $this->alg = $alg;
        $this->DB = new DB();
    }

    /**
     * 生成JWT Token
     * @return [string] 生成的Token | null(数据库存储失败)
     */
    public function create()
    {
        $header = base64_encode(json_encode(array('type' => $this->type,'alg'=>$this->alg )));
        $playload = base64_encode(json_encode(array('userId' => $this->userId,'timestamp'=>$this->timestamp)));
        $token = $header.'.'.$playload.'.'.self::signature($header.'.'.$playload, md5($this->signKey), $this->alg);
        $res = $this->DB->insert('fileshare.token', ['id'=>sha1($this->userId.$this->timestamp),'userId'=>$this->userId,'valid'=>true,'ctime'=>$this->timestamp,'hasUploaded'=>0]);

        if (!$res->getInsertedCount()) {
            return null;
        } else {
            return $token;
        }
    }

    /**
     * 检测用户是否能够再上传文件
     * @param  [string]  $token [Token]
     * @return boolean        [true/false]
     */
    public function hasRightToUpload($token, $filesize)
    {
        $items = explode('.', $token);
        list($header, $payload, $sign) = $items;
        $payload = json_decode(base64_decode($payload), true);
        $id = sha1($payload['userId'].$payload['timestamp']);
        $cursor = $this->DB->query('fileshare.token', ['id' => $id]);
        foreach ($cursor as $document) {
            $hasUploaded = $document->hasUploaded;
            if (($hasUploaded+$filesize)>20*1024*1024*1024) {
                return false;
            }
        }

        return true;
    }

    /**
     * 更新token
     * @param  [string] $token    [Token]
     * @param  [int] $filesize [上传文件大小]
     * @return [boolean]           [true/false]
     */
    public function update($token, $filesize)
    {
        $items = explode('.', $token);
        list($header, $payload, $sign) = $items;
        $payload = json_decode(base64_decode($payload), true);
        $id = sha1($payload['userId'].$payload['timestamp']);
        $cursor = $this->DB->query('fileshare.token', ['id' => $id]);
        foreach ($cursor as $document) {
            $hasUploaded = $document->hasUploaded;
            $this->DB->update('fileshare.token', ['id' => $id], ['hasUploaded'=>$hasUploaded+$filesize]);
        }

        return true;
    }

    /**
     * 验证Token
     * @param  [Token] $token [需要验证的Token]
     * @return [array]        [code=>0/1 Token无效/有效]
     */
    public function verify($token)
    {
        $code = 0;
        $items = explode('.', $token);
        $key    = md5($this->signKey);

        if (count($items) != 3) {
            goto end;
        }

        list($header, $payload, $sign) = $items;

        $headerDecode = json_decode(base64_decode($header), true);
        if (empty($headerDecode['alg'])) {
            goto end;
        }

        if ($this->signature($header . '.' . $payload, $key, $headerDecode['alg']) !== $sign) {
            goto end;
        }

        $payload = json_decode(base64_decode($payload), true);

        $time = time();
        if (isset($payload['timestamp']) &&  ($time-$payload['timestamp'])>24*60*60) {
            goto end;
        }

        $code = 1;

        end:
        return $code;
        ;
    }

    /**
     * 清除数据库中的过期Token记录
     * @return
     */
    public function clean()
    {
        $deleteArray = [];
        # 删除数据库中的过期token
        $cursor = $this->DB->query('fileshare.token', ['valid' => true]);
        foreach ($cursor as $document) {
            $ctime = $document->ctime;
            if ((time()-$ctime)>24*60*60) {
                $deleteArray[]=$document->id;
            }
        }
        foreach ($deleteArray as $id) {
            $this->DB->delete('fileshare.token', ['id'=>$id]);
        }
    }

    /**
     * 生成JWT签名
     * @param  string $input [JWT头部和Playload部分用.号相连]
     * @param  string $key   [签名秘钥md5编码后的值]
     * @param  string $alg   [签名算法]
     * @return [string]        [签名]
     */
    public static function signature(string $input, string $key, string $alg)
    {
        return hash_hmac($alg, $input, $key);
    }
}
